#include "tls_tcp.h"
#include <cstring>
#include <stdint.h>
#include <stdio.h>
#include <string>
#include <sys/time.h>
#include <thread>
#include <utils/logger.h>

namespace communication {

#define CA_CRT                                                             \
    "-----BEGIN CERTIFICATE-----\r\n"                                      \
    "MIIDQTCCAimgAwIBAgIEGP1BjTANBgkqhkiG9w0BAQsFADBRMQswCQYDVQQGEwJD\r\n" \
    "TjELMAkGA1UECBMCU0gxCzAJBgNVBAcTAlNIMQwwCgYDVQQKEwNMRU8xDDAKBgNV\r\n" \
    "BAsTA0xFTzEMMAoGA1UEAxMDTEVPMB4XDTIyMDYwMjAyMjk0MVoXDTQyMDIxNzAy\r\n" \
    "Mjk0MVowUTELMAkGA1UEBhMCQ04xCzAJBgNVBAgTAlNIMQswCQYDVQQHEwJTSDEM\r\n" \
    "MAoGA1UEChMDTEVPMQwwCgYDVQQLEwNMRU8xDDAKBgNVBAMTA0xFTzCCASIwDQYJ\r\n" \
    "KoZIhvcNAQEBBQADggEPADCCAQoCggEBAMFElJuL+X527wiiU4FJ+9UGLH5ulWMx\r\n" \
    "klwJygfDnLESoCbGJKLzQ0HkU5M6lM3ZncqPYaV/IhoB1cGWzTjse0PMd5wcU2pp\r\n" \
    "5zb0B4+2/6UTXS7MZzNOU3mEqv6Yit+G8kV3PteNsmlEJ6WYfbcXHJmfynO/yp4G\r\n" \
    "sBvl1GqOJ3m03bNQZX0DoSVy7KU24aneW9NDfTz5JL0G+iz/kvY5RNk5CCClBLUM\r\n" \
    "bWrdctn4QRqCA3hQN00fEK57HcvU09L21pVwKrw81Y2rBEvNbLQBliJ6ObvuTOEP\r\n" \
    "0G3roUqFi2wI4F9fSTfhXcNwvfkOGue+XimXxnoDlvVUSed9qlLKy4MCAwEAAaMh\r\n" \
    "MB8wHQYDVR0OBBYEFKsl0omv0US8Wl7uEeA7VDQg2h9hMA0GCSqGSIb3DQEBCwUA\r\n" \
    "A4IBAQA4rqzAlCf8RUjHRd7E+CJibOXUl3UNz8C2I3QP8RUljdS/Pt0DcoHj4yWT\r\n" \
    "yIXNSkJfd3SGheQNxdu9W7ePKUpVvgtX0GbsPDG7UKmC6zrSE0OXUs3rdIOnuzAm\r\n" \
    "HLKxO5Z9x06C4kffi9HKFptmrauNR8w5Ej7BB3VktQQkNg+WQYFAOYCPhENca633\r\n" \
    "H2OFeGd4z+6f5oJ3gRZOQNjA5eSL2MSzzWbBrIVFzgmbcrCCYJbQwd4Jwh/JQxn4\r\n" \
    "Xd5GUTeDrBFVD2hZM3vqMc+Gukv8EEz2/D7ZquBtVp3+pXdPKmQsSKHbh967vN52\r\n" \
    "avq0AtIyXcsmXwkF5YN+ezP54ZP9\r\n"                                     \
    "-----END CERTIFICATE-----\r\n"

const char ssl_perm[] = CA_CRT;

TlsTcp::TlsTcp() : lockSsl_(true)
{
    // LOG_ENABLE(false);
}

TlsTcp::~TlsTcp()
{
    destroyTls();
}

std::string TlsTcp::name() const
{
    return std::string("tls tcp");
}

bool TlsTcp::Connect(const std::string &host, int port)
{
    HTELINK_LOG_INFO("lock server_host:%s, port:%d\r\n", host.c_str(), port);
    AutoLock lock(lockSsl_);

    if (!initTls()) {
        return false;
    }

    int ret = 0;
    uint32_t flags;
    connect_ = false;

    HTELINK_LOG_INFO("server_host:%s, port:%d\r\n", host.c_str(), port);
    if ((ret = mbedtls_net_connect(&serverFd_, host.c_str(), std::to_string(port).c_str(), MBEDTLS_NET_PROTO_TCP)) !=
        0) {
        HTELINK_LOG_ERR(" failed\n  ! _mbedtls_net_connect returned 0x%04X\n\n", ret);
        return false;
    }

    host_ = host;
    port_ = port;

    HTELINK_LOG_DEBUG("connect..................server_fd:%d\r\n", serverFd_.fd);

    /*
     * 2. Setup stuff
     */
    HTELINK_LOG_DEBUG("  . Setting up the SSL/TLS structure...\n");

    if ((ret = mbedtls_ssl_config_defaults(
             &sslConf_, MBEDTLS_SSL_IS_CLIENT, MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT)) != 0) {
        HTELINK_LOG_ERR(" failed\n  ! mbedtls_ssl_config_defaults returned %d\n\n", ret);
        return false;
    }

    /* OPTIONAL is not optimal for security,
     * but makes interop easier in this simplified example */
    // mbedtls_ssl_conf_authmode(&conf, MY_MBEDTLS_SSL_VERIFY_OPTIONAL);
    mbedtls_ssl_conf_authmode(&sslConf_, MBEDTLS_SSL_VERIFY_REQUIRED);
    mbedtls_ssl_conf_ca_chain(&sslConf_, &cacert_, NULL);
    mbedtls_ssl_conf_rng(&sslConf_, mbedtls_ctr_drbg_random, &ctrDrbg_);
    mbedtls_ssl_conf_dbg(&sslConf_, TlsTcp::TlsDebug, stdout);
    mbedtls_ssl_conf_read_timeout(&sslConf_, 10000);

    if ((ret = mbedtls_ssl_setup(&sslContext_, &sslConf_)) != 0) {
        HTELINK_LOG_ERR(" failed\n  ! mbedtls_ssl_setup returned %d\n\n", ret);
        return false;
    }

    if ((ret = mbedtls_ssl_set_hostname(&sslContext_, "LEO")) != 0) {
        HTELINK_LOG_ERR(" failed\n  ! mbedtls_ssl_set_hostname returned %d\n\n", ret);
        return false;
    }

    mbedtls_ssl_set_bio(&sslContext_, &serverFd_, mbedtls_net_send, mbedtls_net_recv, mbedtls_net_recv_timeout);

    /*
     * 4. Handshake
     */
    HTELINK_LOG_DEBUG("  . Performing the SSL/TLS handshake..., server_fd %d\n", serverFd_.fd);

    int times = 0;
    while ((ret = mbedtls_ssl_handshake(&sslContext_)) != 0 && ++times < 100) {
        if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) {
            HTELINK_LOG_ERR(" failed\n  ! mbedtls_ssl_handshake returned -0x%x\n\n", -ret);
            return false;
        }
    }

    /*
     * 5. Verify the server certificate
     */
    HTELINK_LOG_DEBUG("  . Verifying peer X.509 certificate...");

    /* In real life, we probably want to bail out when ret != 0 */
    if ((flags = mbedtls_ssl_get_verify_result(&sslContext_)) != 0) {
        char vrfy_buf[512];
        HTELINK_LOG_ERR("verify failed, %s", strerror(errno));
        mbedtls_x509_crt_verify_info(vrfy_buf, sizeof(vrfy_buf), "  ! ", flags);
        HTELINK_LOG_DEBUG("%s\n", vrfy_buf);
        return false;
    } else {
        HTELINK_LOG_INFO("verify ok\n");
        connect_ = true;
        return true;
    }
}

void TlsTcp::DisConnect()
{
    destroyTls();
}

int TlsTcp::SendWithReply(const char *data, int len, char reply[])
{
    AutoLock lock(lockSsl_);
    if (!SendData(data, len)) {
        return 0;
    }
    return RecvData(reply);
}

int32_t TlsTcp::SendData(const char *data, int len)
{
    AutoLock lock(lockSsl_);
    int ret = 0;
    int times = 0;
    HTELINK_LOG_DEBUG("mbedtls_ssl_write len = %d, %s\n", len, len > 20 ? &data[20] : "empty");
    while ((ret = mbedtls_ssl_write(&sslContext_, (unsigned char *)data, len)) <= 0 && ++times < 100) {
        /* code */
        if (ret == MBEDTLS_ERR_SSL_WANT_WRITE) {
            continue;
        }
        char error[256];
        mbedtls_strerror(ret, error, 256);
        HTELINK_LOG_ERR("mbedtls_ssl_write failed, %d, %s\n", ret, error);
        return ret;
    }
    HTELINK_LOG_DEBUG("mbedtls_ssl_write end, len = %d, actual len = %d\n", ret, len);
    return ret;
}

int TlsTcp::RecvData(char reply[], int max, int timeout)
{
    AutoLock lock(lockSsl_);

    int ret = mbedtls_ssl_read(&sslContext_, (unsigned char *)reply, max);

    if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_TIMEOUT) {
        return 0;
    }

    if (ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) {
        HTELINK_LOG_ERR("mbedtls_ssl_read close notify\n");
        return ret;
    }

    if (ret < 0) {
        char error[256];
        mbedtls_strerror(ret, error, 256);
        HTELINK_LOG_ERR("failed\n  ! mbedtls_ssl_read returned %d, %s\n\n", ret, error);
        return ret;
    }
    return ret;
}

void TlsTcp::TlsDebug(void *ctx, int level, const char *file, int line, const char *str)
{
    ((void)level);
    // fprintf(stdout, "%s:%04d: %s", file, line, str );
    // fflush(stdout);
    // HTELINK_LOG_DEBUG("failed\n  ! mbedtls_ssl_read returned %d, %s\n\n", ret, error);
}

bool TlsTcp::initTls()
{
    AutoLock lock(lockSsl_);
    int ret = 1, len;
    int exitCode = MBEDTLS_EXIT_FAILURE;
    const char *PERS = "ssl_client1";

    connect_ = false;
    init_ = false;
    /*
     * 0. Initialize the RNG and the session data
     */
    mbedtls_net_init(&serverFd_);
    mbedtls_ssl_init(&sslContext_);
    mbedtls_ssl_config_init(&sslConf_);
    mbedtls_x509_crt_init(&cacert_);
    mbedtls_ctr_drbg_init(&ctrDrbg_);

    mbedtls_entropy_init(&entropyContext_);

    psa_status_t status = psa_crypto_init();
    if (status != PSA_SUCCESS) {
        HTELINK_LOG_ERR("Failed to initialize PSA Crypto implementation: %d\n", (int)status);
        return false;
    }

    // mbedtls_ssl_conf_rng(&conf, )

    if ((ret = mbedtls_ctr_drbg_seed(
             &ctrDrbg_, mbedtls_entropy_func, &entropyContext_, (const unsigned char *)PERS, strlen(PERS))) != 0) {
        HTELINK_LOG_ERR(" failed! mbedtls_ctr_drbg_seed returned %d\n", ret);
        return false;
    }

#ifdef MBEDTLS_DEBUG_C
    mbedtls_debug_set_threshold(1);
#endif

    ret = mbedtls_x509_crt_parse(&cacert_, (const unsigned char *)ssl_perm, strlen(ssl_perm) + 1);

    if (ret < 0) {
        HTELINK_LOG_ERR(" failed!  mbedtls_x509_crt_parse returned -0x%x\n\n", -ret);
        return false;
    }
    init_ = true;
    return true;
}

void TlsTcp::destroyTls()
{
    AutoLock lock(lockSsl_);
    if (init_ == false) {
        return;
    }
    mbedtls_net_free(&serverFd_);
    mbedtls_x509_crt_free(&cacert_);
    mbedtls_ssl_free(&sslContext_);
    mbedtls_ssl_config_free(&sslConf_);
    mbedtls_ctr_drbg_free(&ctrDrbg_);
    mbedtls_entropy_free(&entropyContext_);
    connect_ = false;
    init_ = false;
}
} // namespace communication
